﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using Ast.Core.Forms;
using Ast.Core.Services;
using Ast.Core.Services.Extensions;
using Ast.Framework.Forms;
using Castle.Core.Logging;

namespace Ast
{
    [FormMetadata(FormType.Object, IsTabForm = true)]
    public partial class ObjectForm : Form
    {
        private readonly IKernelAddressFinder _kernelAddressFinder;
        private readonly IObjectTypeService _objectTypeService;
        private readonly IDeviceController _deviceController;

        public ObjectForm(IKernelAddressFinder kernelAddressFinder, IObjectTypeService objectTypeService, IDeviceController deviceController)
        {
            _kernelAddressFinder = kernelAddressFinder;
            _objectTypeService = objectTypeService;
            _deviceController = deviceController;

            InitializeComponent();

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        private void ObjectForm_Load(object sender, System.EventArgs e)
        {
            lvObjects.ApplyColumnSorter();
            btnReload.PerformClick();
        }

        private async void btnReload_Click(object sender, System.EventArgs e)
        {
            lvObjects.Items.Clear();
            tvDirectories.Nodes.Clear();
            var rootNode = new TreeNode("Loading ...");
            tvDirectories.Nodes.Add(rootNode);

            rootNode = await Task.Run(() =>
            {
                var obpRootDirectoryObject = _kernelAddressFinder.FindObpRootDirectoryObject();
                var rootDirectoryObjectPtr = _deviceController.ReadAsIntPtr(obpRootDirectoryObject).Value;

                rootNode = new TreeNode("Root \\");
                ProcessDirectory(rootNode, rootDirectoryObjectPtr);
                return rootNode;
            }).ConfigureAwait(false);

            // update view
            Invoke(new Action(() =>
            {
                tvDirectories.BeginUpdate();
                tvDirectories.Nodes.Clear();
                tvDirectories.Nodes.Add(rootNode);
                rootNode.Expand();
                tvDirectories.SelectedNode = rootNode;
                tvDirectories.Focus();
                tvDirectories.EndUpdate();
            }));
        }

        private void ProcessDirectory(TreeNode treeNode, IntPtr dirPtr)
        {
            if (dirPtr == IntPtr.Zero) return;

            var objects = new List<DirectoryTreeNodeTag>();
            _objectTypeService.ListObjectsInDirectory(dirPtr, (objectDirectory, objectDirectoryEntry) =>
            {
                var objectType = _objectTypeService.GetObjectType(objectDirectoryEntry.Object);
                if (objectType == null)
                {
                    Logger.WarnFormat("found unknown object type at 0x{0:x}", objectDirectoryEntry.Object.ToInt32());
                    return true;
                }

                var objectName = _objectTypeService.GetObjectName(objectDirectoryEntry.Object);
                var objectShortName = Regex.Match(objectName, "(?<=.+)[^\\\\]+$", RegexOptions.Compiled).Value; // get name from last index of '\'

                if (objectType.Name.Equals("Directory", StringComparison.InvariantCultureIgnoreCase))
                {
                    var childTreeNode = new TreeNode(objectShortName);
                    ProcessDirectory(childTreeNode, objectDirectoryEntry.Object); // process child directory
                    treeNode.Nodes.Add(childTreeNode);
                }
                else
                {
                    var tag = new DirectoryTreeNodeTag
                    {
                        Address = objectDirectoryEntry.Object,
                        ObjectName = objectName,
                        ObjectShortName = objectShortName,
                        ObjectTypeData = objectType
                    };

                    if (objectType.Name.Equals("SymbolicLink", StringComparison.InvariantCultureIgnoreCase))
                    {
                        tag.SymbolicLink = _objectTypeService.GetSymbolicLinkObjectLinkTarget(objectDirectoryEntry.Object);
                    }

                    objects.Add(tag);
                }

                return true;
            });

            treeNode.Tag = objects; // set tag
            treeNode.Text = string.Format("{0} ({1})", treeNode.Text, objects.Count);
        }

        private void tvDirectories_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (tvDirectories.SelectedNode == null) return;

            // update view
            lvObjects.BeginUpdate();
            lvObjects.Items.Clear();
            var objects = tvDirectories.SelectedNode.Tag as List<DirectoryTreeNodeTag>;
            if (objects != null)
            {
                foreach (var @object in objects)
                {
                    var listViewItem = new ListViewItem(new[]
                    {
                        string.Format("0x{0:x}", @object.Address.ToInt32()),
                        @object.ObjectTypeData.Name,
                        @object.ObjectShortName,
                        @object.SymbolicLink
                    })
                    {
                        Tag = @object
                    };
                    lvObjects.Items.Add(listViewItem);
                }
            }
            lvObjects.AutoResizeColumns();
            lvObjects.EndUpdate();
        }

        private void lvObjects_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (lvObjects.SelectedItems.Count == 0) return;

            var tag = (DirectoryTreeNodeTag) lvObjects.SelectedItems[0].Tag;
            lbObjectName.Text = tag.ObjectName;
        }

        private void lbObjectName_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            Clipboard.SetText(lbObjectName.Text);
        }
    }

    public class DirectoryTreeNodeTag
    {
        public IntPtr Address { get; set; }
        public string ObjectName { get; set; }
        public string ObjectShortName { get; set; }
        public string SymbolicLink { get; set; }
        public ObjectTypeData ObjectTypeData { get; set; }
    }
}